// Not much point if we have no assertions.
#ifdef NDEBUG
#undef NDEBUG
#endif

#include <algorithm>
#include <cassert>
#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>
#include "u1.h"
#include "coratings.h"
#include "io.h"
#include "toplist.h"
#include "symmetric_matrix.h"

#define EPS 0.00001

// Like raw ratings file (ordered by movie or user).
#define BLOCK_RATINGS_1    \
    "123:\n"               \
    "111,1,2007-01-20\n"   \
    "112,2.3,1992-07-13\n" \
    "124:\n"               \
    "113,3,1965-10-02\n"

// Like residuals file.
#define BLOCK_RATINGS_2    \
    "123:\n"               \
    "111,1\n"              \
    "112,2.3\n"            \
    "124:\n"               \
    "113,3\n" 

// Like qualifying set file.
#define QUAL_1             \
    "123:\n"               \
    "111,2001-03-31\n"     \
    "112,1972-10-30\n"     \
    "124:\n"               \
    "113\n" 

// Like residuals file. Imagine 123 is a movie (movie blocks).
#define CORATINGS_1        \
    "123:\n"               \
    "111,1\n"              \
    "112,3\n"              \
    "124:\n"               \
    "111,5\n"              \
    "125:\n"               \
    "112,2\n"              \
    "113,4\n" 

using namespace std;

struct F_test_coratings_1
{
  ostringstream os;
  void begin_pair(int id_i, int id_j) {
    os<<id_i<<" "<<id_j<<":";
  }
  void corating(int id, float rating_i, float rating_j) {
    os<<id<<" "<<rating_i<<" "<<rating_j<<",";
  }
  void end_pair() {
    os<<'\n';
  }
};

void test_coratings_1()
{
  istringstream is(CORATINGS_1);
  const size_t r_len = 20;
  id_rating_t<float> r[r_len];
  size_t n = read_ratings_blocks(r, r_len, is);
  assert(n == 9);
  assert(is_movie(r, 0) && r[0].id == 123);
  assert(r[1].id == 111 && r[1].rating == 1);
  assert(r[2].id == 112 && r[2].rating == 3);
  assert(is_movie(r, 3) && r[3].id == 124);
  assert(r[4].id == 111 && r[4].rating == 5);
  assert(is_movie(r, 5) && r[5].id == 125);
  assert(r[6].id == 112 && r[6].rating == 2);
  assert(r[7].id == 113 && r[7].rating == 4);
  assert(is_movie(r, 8) && r[8].id == BAD_ID);
  F_test_coratings_1 test;
  for_coratings(r, n, test);
  assert(test.os.str() ==
    "123 123:111 1 1,112 3 3,\n"
    "123 124:111 1 5,\n"
    "123 125:112 3 2,\n"
    "124 124:111 5 5,\n"
    "124 125:\n"
    "125 125:112 2 2,113 4 4,\n");
}

void test_io_ratings_1()
{
  istringstream is(BLOCK_RATINGS_1);
  ratings_by_user_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.user_id() == 123 && ts.movie_id() == 111 && ts.rating() == 1);
  ++ts;
  assert(ts);
  assert(ts.user_id() == 123 && ts.movie_id() == 112 && abs(ts.rating() - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.user_id() == 124 && ts.movie_id() == 113 && ts.rating() == 3);
  ++ts;
  assert(!ts);
}

void test_io_ratings_2()
{
  istringstream is(BLOCK_RATINGS_1);
  ratings_by_movie_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.movie_id() == 123 && ts.user_id() == 111 && ts.rating() == 1);
  ++ts;
  assert(ts);
  assert(ts.movie_id() == 123 && ts.user_id() == 112 && abs(ts.rating() - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.movie_id() == 124 && ts.user_id() == 113 && ts.rating() == 3);
  ++ts;
  assert(!ts);
}

void test_io_ratings_3()
{
  istringstream is(BLOCK_RATINGS_2);
  ratings_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.block_id() == 123 && ts.id() == 111 && ts.rating() == 1);
  ++ts;
  assert(ts);
  assert(ts.block_id() == 123 && ts.id() == 112 && abs(ts.rating() - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.block_id() == 124 && ts.id() == 113 && ts.rating() == 3);
  ++ts;
  assert(!ts);
}

void test_io_ratings_4()
{
  istringstream is(BLOCK_RATINGS_1);
  ratings_by_user_block_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.size() == 2);
  assert(ts.user_id() == 123 
    && ts.movie_id(0) == 111 && ts.rating(0) == 1
    && ts.movie_id(1) == 112 && abs(ts.rating(1) - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.user_id() == 124
    && ts.movie_id(0) == 113 && ts.rating(0) == 3);
  ++ts;
  assert(!ts);
}

void test_io_ratings_5()
{
  istringstream is(BLOCK_RATINGS_1);
  ratings_by_movie_block_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.size() == 2);
  assert(ts.movie_id() == 123 
    && ts.user_id(0) == 111 && ts.rating(0) == 1
    && ts.user_id(1) == 112 && abs(ts.rating(1) - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.movie_id() == 124
    && ts.user_id(0) == 113 && ts.rating(0) == 3);
  ++ts;
  assert(!ts);
}

void test_io_ratings_6()
{
  istringstream is(CORATINGS_1);
  ratings_by_movie_cursor_t<float> ts(is);
  assert(ts);
  assert(ts.movie_id() == 123 && ts.user_id() == 111 && ts.rating() == 1);
  assert(++ts);
  assert(ts.movie_id() == 123 && ts.user_id() == 112 && ts.rating() == 3);
  assert(++ts);
  assert(ts.movie_id() == 124 && ts.user_id() == 111 && ts.rating() == 5);
  assert(++ts);
  assert(ts.movie_id() == 125 && ts.user_id() == 112 && ts.rating() == 2);
  assert(++ts);
  assert(ts.movie_id() == 125 && ts.user_id() == 113 && ts.rating() == 4);
  assert(!(++ts));
}

void test_io_ratings_7()
{
  istringstream is(BLOCK_RATINGS_1);
  id_rating_t<float> r[6];
  size_t n = read_ratings_blocks(r, 6, is);
  assert(n == 6);
  typedef memory_ratings_by_movie_block_cursor_t<float> ts_t;
  ts_t ts(r, n);

  // Check results using indexes.
  assert(ts);
  assert(ts.size() == 2);
  assert(ts.movie_id() == 123 
    && ts.user_id(0) == 111 && ts.rating(0) == 1
    && ts.user_id(1) == 112 && abs(ts.rating(1) - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.movie_id() == 124
    && ts.user_id(0) == 113 && ts.rating(0) == 3);
  ++ts;
  assert(!ts);

  // Check results using iterators.
  ts.home();
  assert(ts);
  assert(ts.size() == 2);
  int i = 0;
  for (ts_t::const_iterator it = ts.begin(); it != ts.end(); ++it) {
    assert(ts.movie_id() == 123);
    switch (i) {
      case 0: assert(it->id == 111 && abs(it->rating - 1) < EPS); break;
      case 1: assert(it->id == 112 && abs(it->rating - 2.3) < EPS); break;
      default: assert(false); // should not get here
    } 
    ++i;
  }
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  i = 0;
  for (ts_t::const_iterator it = ts.begin(); it != ts.end(); ++it) {
    assert(ts.movie_id() == 124);
    switch (i) {
      case 0: assert(it->id == 113 && abs(it->rating - 3) < EPS); break;
      default: assert(false); // should not get here
    } 
    ++i;
  }
  ++ts;
  assert(!ts);
}

void test_io_ratings_8()
{
  istringstream is(BLOCK_RATINGS_1);
  id_rating_t<float> r[6];
  size_t n = read_ratings_blocks(r, 6, is);
  assert(n == 6);
  index_ratings_by_user_block_cursor_t<float> ts(r, n);
  
  // check forward iteration
  assert(ts);
  assert(ts.size() == 2);
  assert(ts.user_id() == 123 
    && ts.movie_id(0) == 111 && ts.rating(0) == 1
    && ts.movie_id(1) == 112 && abs(ts.rating(1) - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.user_id() == 124
    && ts.movie_id(0) == 113 && ts.rating(0) == 3);
  ++ts;
  assert(!ts);

  // check jumping
  ts.home();
  assert(ts);
  ts.jump(124);
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.user_id() == 124
    && ts.movie_id(0) == 113 && ts.rating(0) == 3);
  ts.jump(123);
  assert(ts);
  assert(ts.size() == 2);
  assert(ts.user_id() == 123 
    && ts.movie_id(0) == 111 && ts.rating(0) == 1
    && ts.movie_id(1) == 112 && abs(ts.rating(1) - 2.3) < EPS);
  ++ts;
  assert(ts);
  assert(ts.size() == 1);
  assert(ts.user_id() == 124
    && ts.movie_id(0) == 113 && ts.rating(0) == 3);
  ++ts;
  assert(!ts);

  // invalid jump should invalidate cursor
  ts.jump(125);
  assert(!ts);
  ts.home();
  assert(ts);
}

void test_io_query_1()
{
  istringstream is(QUAL_1);
  query_by_user_cursor_t qs(is);
  assert(qs);
  assert(qs.user_id() == 123 && qs.movie_id() == 111);
  assert(++qs);
  assert(qs.user_id() == 123 && qs.movie_id() == 112);
  assert(++qs);
  assert(qs.user_id() == 124 && qs.movie_id() == 113);
  ++qs;
  assert(!qs);
}

void test_io_query_2()
{
  istringstream is(QUAL_1);
  query_by_movie_cursor_t qs(is);
  assert(qs);
  assert(qs.movie_id() == 123 && qs.user_id() == 111);
  assert(++qs);
  assert(qs.movie_id() == 123 && qs.user_id() == 112);
  assert(++qs);
  assert(qs.movie_id() == 124 && qs.user_id() == 113);
  ++qs;
  assert(!qs);
}

void test_mean_stddev_1()
{
  vector<float> x;
  x.push_back(0);
  float t;
  t = mean<float, float>(x); assert(t == 0);
  t = variance<float, float>(x); assert(t == 0);
  x.push_back(1);
  t = mean<float, float>(x); assert(t == 0.5);
  t = variance<float, float>(x); assert(t == 0.5);
  x.push_back(1);
  t = mean<float, float>(x); assert(abs(t - 2.0/3) < EPS);
  t = variance<float, float>(x); assert(abs(t - 1.0/3) < EPS);
}

void test_pearson_1()
{
  vector<float> x, y;
  x.push_back(0);
  y.push_back(0);
  x.push_back(1);
  y.push_back(1);
  assert(abs(1 - pearson<float>(x,y)) < EPS);
  x.push_back(1.5);
  y.push_back(1.5);
  assert(abs(1 - pearson<float>(x,y)) < EPS);

  x.clear();
  y.clear();
  x.push_back(0);
  y.push_back(1);
  x.push_back(1);
  y.push_back(0);
  assert(abs(-1 - pearson<float>(x,y)) < EPS);

  x.clear();
  y.clear();
  x.push_back(0);
  y.push_back(0);
  x.push_back(1);
  y.push_back(0);
  assert(abs(1 - pearson<float>(x,y)) < EPS);

  // Example from codecogs
  x.clear();
  y.clear();
  x.push_back(4);
  x.push_back(5);
  x.push_back(8);
  x.push_back(6);
  x.push_back(3);
  y.push_back(6);
  y.push_back(7);
  y.push_back(8);
  y.push_back(3);
  y.push_back(2);
  assert(abs(0.592494 - pearson<float>(x,y)) < EPS);
}

void test_toplist_1()
{
  toplist<int> t(3);
  t.insert(1);
  assert(t.size() == 1);
  t.insert(2);
  assert(t.size() == 2);
  t.insert(3);
  assert(t.size() == 3);
  t.insert(4);
  assert(t.size() == 3);
  assert(find(t.begin(), t.end(), 1) == t.end());
  t.insert(5);
  assert(t.size() == 3);
  assert(find(t.begin(), t.end(), 2) == t.end());
  t.insert(2);
  assert(t.size() == 3);
  assert(find(t.begin(), t.end(), 2) == t.end());
}

void test_symmetric_matrix_1()
{
  symmetric_matrix<float> t(3);
  t(0,0) = 1;
  assert(t(0,0) == 1);
  t(0,1) = 2;
  assert(t(1,0) == 2);
  t(2,0) = 3;
  assert(t(0,2) == 3);

  t.resize(4);
  t(3,1) = 4;
  assert(t(1,3) == 4);
  t(3,2) = 5;
  assert(t(2,3) == 5);
  t(3,3) = 6;
  assert(t(3,3) == 6);
  t /= 2;
  assert(t(3,3) == 3);
  t *= 3;
  assert(t(3,3) == 9);
  t += 1;
  assert(t(3,3) == 10);
}

void test_symmetric_matrix_2()
{
  symmetric_matrix<float> A(2);
  vector<float> x(2), b;
  A(0,0) = 1;
  A(0,1) = 0;
  A(1,1) = 1;
  x[0] = 1;
  x[1] = 2;
  A.left_multiply(x, b);
  assert(b.size() == 2);
  assert(b[0] == 1);
  assert(b[1] == 2);
  assert(abs(A.quadratic(x) - 5) < EPS);
  A(0,1) = 0.5;
  A.left_multiply(x, b);
  assert(b.size() == 2);
  assert(b[0] == 2);
  assert(b[1] == 2.5);
}

void test_vector_1()
{
  vector<float> x(2);
  x[0] = 0.5;
  x[1] = 1.0;
  assert(abs(x * x - 1.25) < EPS);
  assert(abs(L2_norm(x) - sqrt(x * x)) < EPS);
  x *= 2.0f;
  assert(x[0] == 1);
  assert(x[1] == 2);
  x += x;
  assert(x[0] == 2);
  assert(x[1] == 4);
}

void test_nonnegative_quadratic_opt_1()
{
  symmetric_matrix<float> A(2);
  vector<float> b(2), x;
  A(0,0) = 1;
  A(1,1) = 1;
  nonnegative_quadratic_opt<float>(A, b, EPS, 10, x);
  assert(abs(x[0]) < EPS);
  assert(abs(x[1]) < EPS);
  b[0] = 1;
  b[1] = 1;
  nonnegative_quadratic_opt<float>(A, b, EPS, 10, x);
  assert(abs(x[0] - 1) < EPS);
  assert(abs(x[1] - 1) < EPS);
  A(0,1) = 0.5;
  nonnegative_quadratic_opt<float>(A, b, EPS, 10, x);
  // Can work out x1^2 + x1*x2 + x2^2 - x1 - x2 by hand (set deriv = 0).
  assert(abs(x[0] - 2.0/3) < EPS);
  assert(abs(x[1] - 2.0/3) < EPS);
}

static void test_combine()
{
  typedef std::multimap<int, int> mm_t;
  mm_t mm;
  mm.insert(make_pair(1, 1));
  mm.insert(make_pair(1, 2));
  mm.insert(make_pair(2, 3));
  mm.insert(make_pair(2, 4));
  typedef std::map<int, float> m_t;
  m_t m;
  F_multimap_average<m_t, mm_t::const_iterator> avg(m);
  combine(mm, avg);
  assert(avg.m.size() == 2);
  m_t::const_iterator it = avg.m.begin();
  assert(it->first == 1);
  assert(it->second == 1.5);
  ++it;
  assert(it->first == 2);
  assert(it->second == 3.5);
}

static void test_count_keys()
{
  std::multimap<int, int> m;
  assert(m.empty());
  assert(count_keys(m) == 0);
  m.insert(make_pair(1, 1));
  assert(count_keys(m) == 1);
  m.insert(make_pair(1, 2));
  assert(count_keys(m) == 1);
  m.insert(make_pair(2, 1));
  assert(count_keys(m) == 2);
  m.insert(make_pair(2, 2));
  assert(count_keys(m) == 2);
  assert(m.size() == 4);
}

static void test_rating_compare()
{
  vector<id_rating_t<float> > r;
  r.push_back(id_rating_t<float>(123,1));
  r.push_back(id_rating_t<float>(125,2));
  r.push_back(id_rating_t<float>(124,2));
  sort(r.begin(), r.end(), F_id_rating_id_less<float>());
  assert(binary_search(r.begin(), r.end(), 123, F_id_rating_id_less<float>()));
  assert(!binary_search(r.begin(), r.end(), 126, F_id_rating_id_less<float>()));
}

static void test_histogram_1()
{
  NatHistogram h(2);
  assert(h.getCount() == 0);
  h.record(0);
  assert(h.getCount(0) == 1);
  assert(h.getCount(2) == 0);
  assert(h.getCount() == 1);
  h.record(1);
  assert(h.getCount(0) == 2);
  assert(h.getCount(2) == 0);
  assert(h.getCount() == 2);
  h.record(2);
  assert(h.getCount(0) == 2);
  assert(h.getCount(2) == 1);
  assert(h.getCount() == 3);
  assert(h.getCountLTE(2) == 3);
  h.record(5);
  assert(h.getCountLTE(2) == 3);
  assert(h.getTotal() == 6); // 1 + 2 + 5 -> 0 + 2 + 4 w/ bucketing
  assert(abs(h.getStdDev() - 1.914854216) < EPS);
  assert(h.getQuantile(0.5) == 0U);
  assert(h.getQuantile(0.6) == 2U);
  assert(h.getQuantile(1.0) == 4U);
}

static void test_histogram_2()
{
  // Check for overflow in mean method.
  NatHistogram h0;
  h0.record(65536, 65536);
  assert(abs(h0.getMean() - 65536) < EPS);
}

int main(int argc, char **argv)
{
  test_coratings_1();
  test_io_ratings_1();
  test_io_ratings_2();
  test_io_ratings_3();
  test_io_ratings_4();
  test_io_ratings_5();
  test_io_ratings_6();
  test_io_ratings_7();
  test_io_ratings_8();
  test_io_query_1();
  test_io_query_2();
  test_mean_stddev_1();
  test_pearson_1();
  test_toplist_1();
  test_symmetric_matrix_1();
  test_symmetric_matrix_2();
  test_vector_1();
  test_nonnegative_quadratic_opt_1();
  test_combine();
  test_count_keys();
  test_rating_compare();
  test_histogram_1();
  test_histogram_2();
  return 0;
}
/*
* Copyright (c) 2009 John Lees-Miller
* 
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
* 
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

